<template>
  <div id="container" ref="containerRef"></div>
</template>

<script>
import G6 from "@antv/g6";
export default {
  mounted() {
    // mocked data
    const mockData = {
      id: "g1",
      name: "Name1",
      label: "538.90",
      businessType: "B",
      tech: 1,
      children: [
        {
          id: "g12",
          name: "Deal with LONG label LONG label LONG label LONG label",
          label: "338.00",
          businessType: "R",
          tech: 0,
        },
        {
          id: "g13",
          name: "Name9",
          label: "100.90",
          businessType: "B",
          tech: 0,
        },
        {
          id: "g14",
          name: "Name12",
          label: "100.00",
          businessType: "G",
          tech: 0,
        },
        {
          id: "g15",
          name: "Name13",
          label: "101.00",
          businessType: "R",
          tech: 0,
        },
        {
          id: "g16",
          name: "Name14",
          label: "102.00",
          businessType: "B",
          tech: 0,
        }
      ],
    };
    // 根据不同类型取不同颜色
    const colors = {
      B: "#5B8FF9",
      R: "#F46649",
      Y: "#EEBC20",
      G: "#5BD8A6",
      DI: "#A7A7A7",
    };

    //  组件props
    const props = {
      data: mockData,
      config: {
        padding: [20, 50],
        defaultLevel: 3,
        defaultZoom: 0.8,
        modes: { default: ["zoom-canvas", "drag-canvas"] },
      },
    };

    const container = document.getElementById("container");
    const width = container.scrollWidth;
    const height = container.scrollHeight || 500;

    // 默认配置
    const defaultConfig = {
      width,
      height,
      modes: {
        default: ["zoom-canvas", "drag-canvas"],
      },
      fitView: true,
      animate: true,
      defaultNode: {
        type: "flow-rect",
      },
      defaultEdge: {
        type: "cubic-horizontal",
        style: {
          stroke: "#1C827F",
          lineCap: "bevel",
          startArrow: {
            path: "M 0,0 L 4,2 L 4,-2 Z",
            fill: "#1C827F",
          },
          endArrow: {
            path: "M 0,0 L 4,2 L 4,-2 Z",
            fill: "#1C827F",
          },
        },
      },
      layout: {
        type: "mindmap",
        direction: "H",
        getWidth: (node) => {
          return 300;
        },
        getHeight: () => {
          return 80;
        },
      },
    };

    // 自定义节点、边
    const registerFn = () => {
      /**
       * 自定义节点
       */
      G6.registerNode(
        "flow-rect",
        {
          shapeType: "flow-rect",
          draw(cfg, group) {
            const { name = "", label, businessType, tech } = cfg;

            const grey = "#CED4D9";
            const rectConfig = {
              width: 202,
              height: 60,
              lineWidth: 1,
              fontSize: 12,
              fill: tech ? "#1c827f" : "#fff",
              radius: tech ? 12 : 4,
              stroke: grey,
              opacity: 1,
            };

            const nodeOrigin = {
              x: -rectConfig.width / 2,
              y: -rectConfig.height / 2,
            };

            const textConfig = {
              textAlign: "left",
              textBaseline: "bottom",
            };

            const rect = group.addShape("rect", {
              attrs: {
                x: nodeOrigin.x,
                y: nodeOrigin.y,
                ...rectConfig,
              },
            });

            const rectBBox = rect.getBBox();

            if (!tech) {
              // label title-背景色加深（使用tinycolor加深颜色）
              group.addShape("text", {
                attrs: {
                  ...textConfig,
                  x: 12 + nodeOrigin.x,
                  y: 20 + nodeOrigin.y,
                  text: name.length > 28 ? name.substr(0, 28) + "..." : name,
                  fontSize: 10,
                  opacity: 0.85,
                  fill: "#000",
                  cursor: "pointer",
                },
                // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
                name: "name-shape",
              });

              // price
              const price = group.addShape("text", {
                attrs: {
                  ...textConfig,
                  x: 8 + nodeOrigin.x,
                  y: rectBBox.maxY - 8,
                  text: label,
                  fontSize: 16,
                  fontWeight: 700,
                  fill: "#000",
                  opacity: 0.85,
                },
              });

              // 顶部线 up back background
              const upBackRect = group.addShape("rect", {
                attrs: {
                  x: nodeOrigin.x,
                  y: -rectBBox.maxY,
                  width: rectConfig.width,
                  height: 28,
                  radius: [rectConfig.radius, rectConfig.radius, 0, 0],
                  fill: colors[businessType],
                  fillOpacity: 0.5,
                },
              });

              // 顶部线 up line background
              const upRect = group.addShape("rect", {
                attrs: {
                  x: nodeOrigin.x,
                  y: -(rectBBox.maxY),
                  width: rectConfig.width,
                  height: 4,
                  radius: [rectConfig.radius, rectConfig.radius, 0, 0],
                  fill: colors[businessType],
                },
              });

              this.drawLinkPoints(cfg, group);
            }else {
              // label title
              group.addShape("text", {
                attrs: {
                  ...textConfig,
                  x: 12 + nodeOrigin.x,
                  y: 20 + nodeOrigin.y,
                  text: name.length > 28 ? name.substr(0, 28) + "..." : name,
                  fontSize: 8,
                  opacity: 0.85,
                  fill: "#fff",
                  cursor: "pointer",
                },
                // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
                name: "name-shape",
              });

              // price
              const price = group.addShape("text", {
                attrs: {
                  ...textConfig,
                  x: 12 + nodeOrigin.x,
                  y: rectBBox.maxY - 12,
                  text: label,
                  fontSize: 16,
                  fontWeight: 700,
                  fill: "#fff",
                  opacity: 0.85,
                },
              });
            }

            return rect;
          },
          update(cfg, item) {
            const { level, businessType, name } = cfg;
            const group = item.getContainer();
            let mask = group.find((ele) => ele.get("name") === "mask-shape");
            let maskLabel = group.find(
              (ele) => ele.get("name") === "mask-label-shape"
            );
            if (level === 0) {
              group.get("children").forEach((child) => {
                if (child.get("name")?.includes("collapse")) return;
                child.hide();
              });
              if (!mask) {
                mask = group.addShape("rect", {
                  attrs: {
                    x: -101,
                    y: -30,
                    width: 202,
                    height: 60,
                    opacity: 0,
                    fill: colors[businessType],
                  },
                  // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
                  name: "mask-shape",
                });
                maskLabel = group.addShape("text", {
                  attrs: {
                    fill: "#fff",
                    fontSize: 20,
                    x: 0,
                    y: 10,
                    text: name.length > 28 ? name.substr(0, 16) + "..." : name,
                    textAlign: "center",
                    opacity: 0,
                  },
                  // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
                  name: "mask-label-shape",
                });
                const collapseRect = group.find(
                  (ele) => ele.get("name") === "collapse-back"
                );
                const collapseText = group.find(
                  (ele) => ele.get("name") === "collapse-text"
                );
                collapseRect?.toFront();
                collapseText?.toFront();
              } else {
                mask.show();
                maskLabel.show();
              }
              mask.animate({ opacity: 1 }, 200);
              maskLabel.animate({ opacity: 1 }, 200);
              return mask;
            } else {
              group.get("children").forEach((child) => {
                if (child.get("name")?.includes("collapse")) return;
                child.show();
              });
              mask?.animate(
                { opacity: 0 },
                {
                  duration: 200,
                  callback: () => mask.hide(),
                }
              );
              maskLabel?.animate(
                { opacity: 0 },
                {
                  duration: 200,
                  callback: () => maskLabel.hide(),
                }
              );
            }
            this.updateLinkPoints(cfg, group);
          },
          setState(name, value, item) {
            if (name === "collapse") {
              const group = item.getContainer();
              const collapseText = group.find(
                (e) => e.get("name") === "collapse-text"
              );
              if (collapseText) {
                if (!value) {
                  collapseText.attr({
                    text: "-",
                  });
                } else {
                  collapseText.attr({
                    text: "+",
                  });
                }
              }
            }
          },
          getAnchorPoints() {
            return [
              [0, 0.5],
              [1, 0.5],
            ];
          },
        },
        "rect"
      );

      G6.registerEdge(
        "flow-cubic",
        {
          getControlPoints(cfg) {
            let controlPoints = cfg.controlPoints; // 指定controlPoints
            if (!controlPoints || !controlPoints.length) {
              const { startPoint, endPoint, sourceNode, targetNode } = cfg;
              const {
                x: startX,
                y: startY,
                coefficientX,
                coefficientY,
              } = sourceNode ? sourceNode.getModel() : startPoint;
              const { x: endX, y: endY } = targetNode
                ? targetNode.getModel()
                : endPoint;
              let curveStart = (endX - startX) * coefficientX;
              let curveEnd = (endY - startY) * coefficientY;
              curveStart = curveStart > 40 ? 40 : curveStart;
              curveEnd = curveEnd < -30 ? curveEnd : -30;
              controlPoints = [
                { x: startPoint.x + curveStart, y: startPoint.y },
                { x: endPoint.x + curveEnd, y: endPoint.y },
              ];
            }
            return controlPoints;
          },
          getPath(points) {
            const path = [];
            path.push(["M", points[0].x, points[0].y]);
            path.push([
              "C",
              points[1].x,
              points[1].y,
              points[2].x,
              points[2].y,
              points[3].x,
              points[3].y,
            ]);
            return path;
          },
        },
        "single-line"
      );
    };

    registerFn();

    const { data } = props;
    let graph = null;

    const initGraph = (data) => {
      if (!data) {
        return;
      }
      const { onInit, config } = props;
      const tooltip = new G6.Tooltip({
        // offsetX and offsetY include the padding of the parent container
        offsetX: 20,
        offsetY: 30,
        // the types of items that allow the tooltip show up
        // 允许出现 tooltip 的 item 类型
        itemTypes: ["node"],
        // custom the tooltip's content
        // 自定义 tooltip 内容
        getContent: (e) => {
          const outDiv = document.createElement("div");
          //outDiv.style.padding = '0px 0px 20px 0px';
          const nodeName = e.item.getModel().name;
          let formatedNodeName = "";
          for (let i = 0; i < nodeName.length; i++) {
            formatedNodeName = `${formatedNodeName}${nodeName[i]}`;
            if (i !== 0 && i % 20 === 0)
              formatedNodeName = `${formatedNodeName}<br/>`;
          }
          outDiv.innerHTML = `${formatedNodeName}`;
          return outDiv;
        },
        shouldBegin: (e) => {
          if (
            e.target.get("name") === "name-shape" ||
            e.target.get("name") === "mask-label-shape"
          )
            return true;
          return false;
        },
      });
      graph = new G6.TreeGraph({
        container: "container",
        ...defaultConfig,
        ...config,
        plugins: [tooltip],
      });
      if (typeof onInit === "function") {
        onInit(graph);
      }
      graph.data(data);
      graph.render();

      const handleCollapse = (e) => {
        const target = e.target;
        const id = target.get("modelId");
        const item = graph.findById(id);
        const nodeModel = item.getModel();
        nodeModel.collapsed = !nodeModel.collapsed;
        graph.layout();
        graph.setItemState(item, "collapse", nodeModel.collapsed);
      };
      graph.on("collapse-text:click", (e) => {
        handleCollapse(e);
      });
      graph.on("collapse-back:click", (e) => {
        handleCollapse(e);
      });

      // 监听画布缩放，缩小到一定程度，节点显示缩略样式
      let currentLevel = 1;
      const briefZoomThreshold = Math.max(graph.getZoom(), 0.5);
      graph.on("viewportchange", (e) => {
        if (e.action !== "zoom") return;
        const currentZoom = graph.getZoom();
        let toLevel = currentLevel;
        if (currentZoom < briefZoomThreshold) {
          toLevel = 0;
        } else {
          toLevel = 1;
        }
        if (toLevel !== currentLevel) {
          currentLevel = toLevel;
          graph.getNodes().forEach((node) => {
            graph.updateItem(node, {
              level: toLevel,
            });
          });
        }
      });
    };

    initGraph(data);
  },
  methods: {},
};
</script>

<style lang="scss" scoped>
.g6-container {
  width: 98%;
  height: 98%;
  position: absolute;
  overflow: hidden;
  z-index: 0;
}
.g6-component-tooltip {
  background-color: rgba(0, 0, 0, 0.65);
  padding: 10px;
  box-shadow: rgb(174, 174, 174) 0px 0px 10px;
  width: fit-content;
  color: #fff;
  border-radius: 4px;
}
</style>
